using System.Collections;

namespace LightCAD.Three
{
    public abstract class Vector
    {
        public abstract bool equals(Vector obj);
    }
    public class Vector2 : Vector, IIOArray
    {
        #region Properties

        public double x;
        public double y;

        #endregion
        public Vector2() : this(0, 0)
        { }
        public Vector2(double x = 0, double y = 0)
        {
            this.x = x;
            this.y = y;
        }
        public double width
        {
            get
            {
                return this.x;
            }
            set
            {
                this.x = value;
            }
        }
        public double height
        {
            get
            {
                return this.y;
            }
            set
            {
                this.y = value;
            }
        }
        public Vector2 set(double x, double y)
        {
            this.x = x;
            this.y = y;
            return this;
        }
        public Vector2 setScalar(double scalar)
        {
            this.x = scalar;
            this.y = scalar;
            return this;
        }
        public Vector2 setX(double x)
        {
            this.x = x;
            return this;
        }
        public Vector2 setY(double y)
        {
            this.y = y;
            return this;
        }
        public Vector2 setComponent(double index, double value)
        {
            switch (index)
            {
                case 0: this.x = value; break;
                case 1: this.y = value; break;
                default: throw new Error("index is out of range: " + index);
            }
            return this;
        }
        public double getComponent(double index)
        {
            switch (index)
            {
                case 0: return this.x;
                case 1: return this.y;
                default: throw new Error("index is out of range: " + index);
            }
        }
        public Vector2 clone()
        {
            return new Vector2(this.x, this.y);
        }
        public Vector2 copy(Vector2 v)
        {
            this.x = v.x;
            this.y = v.y;
            return this;
        }
        public Vector2 add(Vector2 v)
        {
            this.x += v.x;
            this.y += v.y;
            return this;
        }
        public Vector2 addScalar(double s)
        {
            this.x += s;
            this.y += s;
            return this;
        }
        public Vector2 addVectors(Vector2 a, Vector2 b)
        {
            this.x = a.x + b.x;
            this.y = a.y + b.y;
            return this;
        }
        public Vector2 addScaledVector(Vector2 v, double s)
        {
            this.x += v.x * s;
            this.y += v.y * s;
            return this;
        }
        public Vector2 sub(Vector2 v)
        {
            this.x -= v.x;
            this.y -= v.y;
            return this;
        }
        public Vector2 subScalar(double s)
        {
            this.x -= s;
            this.y -= s;
            return this;
        }
        public Vector2 subVectors(Vector2 a, Vector2 b)
        {
            this.x = a.x - b.x;
            this.y = a.y - b.y;
            return this;
        }
        public Vector2 multiply(Vector2 v)
        {
            this.x *= v.x;
            this.y *= v.y;
            return this;
        }
        public Vector2 multiplyScalar(double scalar)
        {
            this.x *= scalar;
            this.y *= scalar;
            return this;
        }
        public Vector2 divide(Vector2 v)
        {
            this.x /= v.x;
            this.y /= v.y;
            return this;
        }
        public Vector2 divideScalar(double scalar)
        {
            return this.multiplyScalar(1 / scalar);
        }
        public Vector2 applyMatrix3(Matrix3 m)
        {
            double x = this.x, y = this.y;
            double[] e = m.elements;
            this.x = e[0] * x + e[3] * y + e[6];
            this.y = e[1] * x + e[4] * y + e[7];
            return this;
        }
        public Vector2 min(Vector2 v)
        {
            this.x = JMath.min(this.x, v.x);
            this.y = JMath.min(this.y, v.y);
            return this;
        }
        public Vector2 max(Vector2 v)
        {
            this.x = JMath.max(this.x, v.x);
            this.y = JMath.max(this.y, v.y);
            return this;
        }
        public Vector2 clamp(Vector2 min, Vector2 max)
        {
            // assumes min < max, componentwise
            this.x = JMath.max(min.x, JMath.min(max.x, this.x));
            this.y = JMath.max(min.y, JMath.min(max.y, this.y));
            return this;
        }
        public Vector2 clampScalar(double minVal, double maxVal)
        {
            this.x = JMath.max(minVal, JMath.min(maxVal, this.x));
            this.y = JMath.max(minVal, JMath.min(maxVal, this.y));
            return this;
        }
        public Vector2 clampLength(double min, double max)
        {
            var length = this.length();
            return this.divideScalar(length == 0 ? 1 : length).multiplyScalar(JMath.max(min, JMath.min(max, length)));
        }
        public Vector2 floor()
        {
            this.x = JMath.floor(this.x);
            this.y = JMath.floor(this.y);
            return this;
        }
        public Vector2 ceil()
        {
            this.x = JMath.ceil(this.x);
            this.y = JMath.ceil(this.y);
            return this;
        }
        public Vector2 round()
        {
            this.x = JMath.round(this.x);
            this.y = JMath.round(this.y);
            return this;
        }
        public Vector2 roundToZero()
        {
            this.x = (this.x < 0) ? JMath.ceil(this.x) : JMath.floor(this.x);
            this.y = (this.y < 0) ? JMath.ceil(this.y) : JMath.floor(this.y);
            return this;
        }
        public Vector2 negate()
        {
            this.x = -this.x;
            this.y = -this.y;
            return this;
        }
        public double dot(Vector2 v)
        {
            return this.x * v.x + this.y * v.y;
        }
        public double cross(Vector2 v)
        {
            return this.x * v.y - this.y * v.x;
        }
        public double lengthSq()
        {
            return this.x * this.x + this.y * this.y;
        }
        public double length()
        {
            return JMath.sqrt(this.x * this.x + this.y * this.y);
        }
        public double manhattanLength()
        {
            return JMath.abs(this.x) + JMath.abs(this.y);
        }
        public Vector2 normalize()
        {
            var len = this.length();
            return this.divideScalar(len == 0 ? 1 : len);
        }
        public double angle()
        {
            // computes the angle in radians with respect to the positive x-axis
            double angle = JMath.atan2(-this.y, -this.x) + JMath.PI;
            return angle;
        }
        public double angleTo(Vector2 v)
        {
            var denominator = JMath.sqrt(this.lengthSq() * v.lengthSq());
            if (denominator == 0) return JMath.PI / 2;
            var theta = this.dot(v) / denominator;

            // clamp, to handle numerical problems

            return JMath.acos(MathUtils.clamp(theta, -1, 1));

        }
        public double distanceTo(Vector2 v)
        {
            return JMath.sqrt(this.distanceToSquared(v));
        }
        public double distanceToSquared(Vector2 v)
        {
            double dx = this.x - v.x, dy = this.y - v.y;
            return dx * dx + dy * dy;
        }
        public double manhattanDistanceTo(Vector2 v)
        {
            return JMath.abs(this.x - v.x) + JMath.abs(this.y - v.y);
        }
        public Vector2 setLength(double length)
        {
            return this.normalize().multiplyScalar(length);
        }
        public Vector2 lerp(Vector2 v, double alpha)
        {
            this.x += (v.x - this.x) * alpha;
            this.y += (v.y - this.y) * alpha;
            return this;
        }
        public Vector2 lerpVectors(Vector2 v1, Vector2 v2, double alpha)
        {
            this.x = v1.x + (v2.x - v1.x) * alpha;
            this.y = v1.y + (v2.y - v1.y) * alpha;
            return this;
        }
        public override bool equals(Vector v)
        {
            return equals(v as Vector2);
        }
        public bool equals(Vector2 v)
        {
            return ((v.x == this.x) && (v.y == this.y));
        }
        public bool equals(IIOArray obj) => equals(obj as Vector2);
        public Vector2 fromArray(double[] array, int offset = 0)
        {
            this.x = array[offset];
            this.y = array[offset + 1];
            return this;
        }
        IIOArray IIOArray.fromArray(double[] array, int offset)
        {
            return fromArray(array, offset);
        }
        public double[] toArray(double[] array = null, int offset = 0)
        {
            if (array == null) array = new double[2];
            array[offset] = this.x;
            array[offset + 1] = this.y;
            return array;
        }
        public Vector2 fromBufferAttribute(BufferAttribute attribute, int index)
        {
            this.x = attribute.getX(index);
            this.y = attribute.getY(index);
            return this;
        }
        public Vector2 rotateAround(Vector2 center, double angle)
        {
            double c = JMath.cos(angle), s = JMath.sin(angle);
            double x = this.x - center.x;
            double y = this.y - center.y;
            this.x = x * c - y * s + center.x;
            this.y = x * s + y * c + center.y;
            return this;
        }
        public Vector2 random()
        {
            this.x = JMath.random();
            this.y = JMath.random();
            return this;
        }

        public IEnumerator GetEnumerator()
        {
            yield return this.x;
            yield return this.y;
        }

        public bool equals(object obj)
        {
            return this.equals(obj as Vector2);
        }

        public Vector3 toVector3()
        {
            return new Vector3(this.x, this.y, 0);
        }

        public override string ToString()
        {
            return $"x: {this.x}, y: {this.y}";
        }
    }
}